package redis

import (
	"context"
	"github.com/go-redis/redis/v7"
	"time"
)

type CacheKey interface {
	Key() string
}
type KeyCacheHandler func(ctx context.Context, key string, next func() (interface{}, error)) (interface{}, error)
type KeyCacheStringHandler func(ctx context.Context, key string, next func() (interface{}, error)) (string, error)
type DelKeyCacheHandler func(ctx context.Context, key string, next func() error) error

// KeyWithCache 处理一般的结构体，如果数组请采用 KeyWithStringCache
func (c *Client) KeyWithCache(timeout time.Duration, rs interface{}) KeyCacheHandler {
	return func(ctx context.Context, key string, next func() (interface{}, error)) (interface{}, error) {
		ok, err := c.SaveOrGetByJson(key, rs, true, 0)
		if err != nil {
			return rs, err
		}
		if ok {
			return rs, nil
		}

		result, dbErr := next()
		if dbErr != nil {
			return rs, dbErr
		}

		_, err = c.SaveOrGetByJson(key, result, false, timeout)
		if err != nil {
			return rs, err
		}
		return result, nil
	}
}
func (c *Client) KeyWithStringCache(timeout time.Duration) KeyCacheStringHandler {
	return func(ctx context.Context, key string, next func() (interface{}, error)) (string, error) {
		rs, err := c.Get(key)
		if err != nil && err != redis.Nil {
			return "", err
		}
		if rs != "" && err != redis.Nil {
			return rs.(string), nil
		}

		result, dbErr := next()
		if dbErr != nil {
			return "", dbErr
		}
		val, err := toJson(result)
		if err != nil {
			return "", err
		}
		if timeout > 0 {
			if err := c.SetWithTTL(key, val, timeout); err != nil {
				return "", err
			}
		} else {
			if err := c.Set(key, val); err != nil {
				return "", err
			}
		}
		return val, nil
	}
}
func (c *Client) DelKeyCache() DelKeyCacheHandler {
	return func(ctx context.Context, key string, next func() error) error {
		dbErr := next()
		if dbErr != nil {
			return dbErr
		}
		err := c.Del(key)
		if err != nil {
			return err
		}
		return nil
	}
}

type CacheHandler func(ctx context.Context, key CacheKey, next func(ctx context.Context, in interface{}) (interface{}, error)) (interface{}, error)
type StringCacheHandler func(ctx context.Context, key CacheKey, next func(ctx context.Context, in interface{}) (interface{}, error)) (string, error)
type DelCacheHandler func(ctx context.Context, key CacheKey, next func(ctx context.Context, in interface{}) error) error

// WithCache 缓存处理装饰器，它接受一个key和一个实际执行业务逻辑的next函数，
// 在执行next前先尝试从缓存获取数据，如果缓存不存在，则调用next并把结果存入缓存。
// 处理一般的结构体，如果数组请采用 WithStringCache
func (c *Client) WithCache(timeout time.Duration, rs interface{}) CacheHandler {
	return func(ctx context.Context, key CacheKey, next func(ctx context.Context, in interface{}) (interface{}, error)) (interface{}, error) {
		cacheKey := key.Key()
		ok, err := c.SaveOrGetByJson(cacheKey, rs, true, 0)
		if err != nil { // 如果从缓存成功获取了数据
			return rs, err
		}
		if ok {
			return rs, nil
		}

		result, dbErr := next(ctx, key)
		if dbErr != nil {
			return rs, dbErr
		}
		_, err = c.SaveOrGetByJson(cacheKey, result, false, timeout)
		if err != nil {
			return rs, err
		}
		return result, nil
	}
}
func (c *Client) WithStringCache(timeout time.Duration) StringCacheHandler {
	return func(ctx context.Context, key CacheKey, next func(ctx context.Context, in interface{}) (interface{}, error)) (string, error) {
		cacheKey := key.Key()
		rs, err := c.Get(cacheKey)
		if err != nil && err != redis.Nil {
			return "", err
		}
		if rs != "" && err != redis.Nil {
			return rs.(string), nil
		}
		result, dbErr := next(ctx, key)
		if dbErr != nil {
			return "", dbErr
		}
		val, err := toJson(result)
		if err != nil {
			return "", err
		}
		if timeout > 0 {
			if err := c.SetWithTTL(cacheKey, val, timeout); err != nil {
				return "", err
			}
		} else {
			if err := c.Set(cacheKey, val); err != nil {
				return "", err
			}
		}
		return val, nil
	}
}
func (c *Client) DelCache() DelCacheHandler {
	return func(ctx context.Context, key CacheKey, next func(ctx context.Context, in interface{}) error) error {
		dbErr := next(ctx, key)
		if dbErr != nil {
			return dbErr
		}
		err := c.Del(key.Key())
		if err != nil {
			return err
		}
		return nil
	}
}
